#include "kernel.h"

struct acpi_rsdp_t
{
	 uint8_t signature[8];			/* ``RSD PTR '' */
	 uint8_t checksum;
	 uint8_t oem_id[6];
	 uint8_t revision;
	 physaddr_t rsdt_phys;
	 uint32_t rsdt_len;
	 uint64_t xrsdt_phys;
	 uint8_t xchecksum;
	 uint8_t __reserved[3];
} __attribute__((packed));

/* standard table header structure */
struct acpi_sdth_t
{
	 uint8_t signature[4];
	 uint32_t length;
	 uint8_t revision;
	 uint8_t checksum;
	 uint8_t oem_id[6];
	 uint64_t oem_table_id;
	 uint32_t oem_revision;
	 uint32_t creator_id;
	 uint32_t creator_revision;
} __attribute__((packed));

struct acpi_madt_t
{
	 physaddr_t lapic_phys;
	 uint32_t flags;
};

struct acpi_madt_apic_desc_t
{
	 uint8_t type;
	 uint8_t length;
	 union
	 {
		  struct
		  {
			   uint8_t proc_id;
			   uint8_t apic_id;
			   uint8_t flags;
		  } lapic;

		  struct
		  {
			   uint8_t apic_id;
			   uint8_t reserved;
			   physaddr_t phys;
			   uint32_t intr_base;
		  } ioapic;
	 };
} __attribute__((packed));

static uint8_t
sum(uint8_t *addr, int len)
{
	 int i, sum;  
	 sum = 0;
	 for(i = 0; i < len; i++)
		  sum += addr[i];
	 return sum;
}

static struct acpi_rsdp_t*
acpi_rsdp_search_segment(uint8_t *addr, int len, uint32_t align, int ex)
{
	 uintptr_t e, p;
	 struct acpi_rsdp_t *result = NULL;
	 
	 e = (uintptr_t)(addr + len) & ~align;
	 p = (uintptr_t)addr;
	 if (p & align) p = (p | align) + 1;
	 for(; p < e; p = p + align + 1)
		  if(memcmp((char *)p, "RSD PTR ", 8) == 0)
		  {
			   if (sum((char *)p, ex ? 36 : 20) == 0)
			   {
					result = (struct acpi_rsdp_t*)p;
					return result;
			   }
		  }
	 return NULL;
}

#define BDA 0x400

// Search for the RSDP, which according to the
// spec is in one of the following three locations:
// 1) in the first KB of the EBDA;
// 3) in the BIOS ROM between 0xE0000 and 0xFFFFF.
static struct acpi_rsdp_t*
acpi_rsdp_search(void)
{
	 uint8_t *bda;
	 uint32_t p;
	 struct acpi_rsdp_t *rsdp;

	 bda = (uint8_t *)KADDR(BDA);
	 p = (uint32_t)KADDR(((bda[0x0F] << 8) | bda[0x0E]) << 4);
	 if (p)
	 {
		  if ((rsdp = acpi_rsdp_search_segment((uint8_t *)p, 1024, 0xf, 1)))
			   return rsdp;
	 }
	 return acpi_rsdp_search_segment((uint8_t *)KADDR(0xE0000), 0x20000, 0xf, 1);
}

int
acpi_init(void)
{
	 struct acpi_rsdp_t *rsdp = acpi_rsdp_search();
	 if (!rsdp)
	 {
		  return -E_UNSUPPORTED;
	 }

	 struct acpi_sdth_t *rsdt = (struct acpi_sdth_t *)KADDR(rsdp->rsdt_phys);
	 int n = (rsdt->length  - sizeof(struct acpi_sdth_t)) / 4;
	 physaddr_t *phys = (physaddr_t *)(rsdt + 1);
	 int i;
	 for (i = 0; i != n; ++ i)
	 {
		  struct acpi_sdth_t *cur = (struct acpi_sdth_t *)KADDR(phys[i]);
		  if (memcmp(cur->signature, "APIC", 4) == 0)
		  {
			   kprintf("Processing MADT\n");
			   struct acpi_madt_t *madt = (struct acpi_madt_t *)(cur + 1);

			   sysconf.lapic_phys = madt->lapic_phys;

			   char *apic_cur = (char *)(madt + 1);
			   char *apic_end = (char *)cur + cur->length;

			   while (apic_cur < apic_end)
			   {
					struct acpi_madt_apic_desc_t *desc =
						 (struct acpi_madt_apic_desc_t *)apic_cur;

					if (desc->type == 0 && desc->lapic.flags & 1)
					{
						 /* Logical Processor, enabled */
						 cpu_ids[sysconf.cpu_count] = desc->lapic.apic_id;
						 cpus[sysconf.cpu_count].valid = 1;
						 sysconf.cpu_count ++;
					}
					else if (desc->type == 1)
					{
						 /* IO APIC */
						 ioapics[sysconf.ioapic_count].id = desc->ioapic.apic_id;
						 ioapics[sysconf.ioapic_count].phys = desc->ioapic.phys;
						 ioapics[sysconf.ioapic_count].intr_base = desc->ioapic.intr_base;
						 ++ sysconf.ioapic_count;
					}

					apic_cur += desc->length;
			   }
		  }
	 }
	 
	 return 0;
}
